#include "gamemap.h"
#include <qfile.h>
#include <qdatastream.h>
#include <qdebug.h>
#include "unitIA.h"

#include "exceptions.h"

GameMap::GameMap(const QString& filePath, Tileset& tileset, const Team& playerTeam,const QString& filePathReal, bool b_special_fabinouz)
    : m_map(filePath, tileset, b_special_fabinouz), m_started(false), m_gameOver(false), m_matrix(0), m_playerTeam(PLAYER_TEAM, playerTeam.getName()), m_computerTeam(COMPUTER_TEAM, "Ennemies"), m_file(filePathReal)
{
    if(!b_special_fabinouz) {
        quint8 charNumber;
        //m_name = m_map.getName();
        m_name = filePathReal;
        m_width = m_map.getWidth();
        m_height = m_map.getHeight();

        //Creates the matrix
        m_matrix = new GameMapSquare*[m_height];
        for(quint8 j = 0 ; j < m_height ; j++)
        {
            m_matrix[j] = new GameMapSquare[m_width];
            for(quint8 i = 0 ; i < m_width ; i++)
            {

                charNumber = m_map.getCharacterNumber(i,j);
                if(charNumber == UNIT_SPAWN && m_playerTeam.size() < playerTeam.size())
                {
                    //if the square contains a spawn point, places an unit of the team if at least one of them remains
                    m_playerTeam.append(new Unit(this, playerTeam.at(m_playerTeam.size())->getProperties(), i, j, PLAYER_TEAM));
                    m_matrix[j][i].unit = m_playerTeam.last();
                }
                else if(charNumber < predefinedUnitsNumber)
                {
                    //if the square contains an ennemy unit, creates it and adds it to the ennemy team
                    m_computerTeam.append(new UnitIA(this, UnitFactory::getInstance().getUnitProperties(charNumber), i, j, COMPUTER_TEAM));   //I'm not a number, I'm a free man !
                    m_matrix[j][i].unit = m_computerTeam.last();
                }
                else
                {
                    m_matrix[j][i].unit = (Unit*)0; //no unit on this square
                }
            }
        }
        m_units.append(m_playerTeam);
        m_units.append(m_computerTeam);
    }
    else {
        read();
    }
}

GameMap::~GameMap()
{
    while(!m_units.isEmpty())
    {
        delete m_units.takeFirst();
    }
    for(quint8 j = 0 ; j < m_height ; j++)
    {
        delete[] m_matrix[j];
    }
    delete[] m_matrix;
}

bool GameMap::gameIsOver() const
{
    return m_gameOver;
}

void GameMap::read() {

    m_file.open(QIODevice::ReadOnly);
    QDataStream inData(&m_file);

    //Reads the map name
    char* name;
    inData >> name;
    m_name = name;
    qDebug() << name;
    delete[] name;

    //Reads the matrix dimensions
    inData >> m_width;
    qDebug() << m_width;
    inData >> m_height;
    qDebug() << m_height;

    //Then reads the matrix entries
    for(quint8 j = 0 ; j < m_height ; j++)
    {
        for(quint8 i = 0 ; i < m_width ; i++)
        {
            quint16 q;
            inData >> q;
            m_map.modifyTile(i,j,q);
        }
    }
    quint16 k=0;
    QString s;
    quint8 n;
    inData >> s;
    inData >> n;
    m_playerTeam.setName(s);
    m_playerTeam.setNumber(n);
    inData >> s;
    inData >> n;
    m_computerTeam.setName(s);
    m_computerTeam.setNumber(n);
    m_matrix = new GameMapSquare*[m_height];
    for(quint8 j = 0 ; j < m_height ; j++)
    {
        m_matrix[j] = new GameMapSquare[m_width];
        for(quint8 i = 0 ; i < m_width ; i++)
        {
            quint8 v;
            inData >> v;
            if (v == 0) {
                m_matrix[j][i].unit = (Unit*)0;
            }
            else {

                UnitProperties props;

                quint8 ordre;
                inData >> ordre;
                inData >> props.charsetPath;
                int h;
                inData >> h;
                props.unitClass = UnitClass(h);
                inData >> h;
                props.unitType = UnitType(h);

                inData >> props.priority;

                inData >> props.level;
                inData >> props.exp;

                inData >> props.maxHealth;
                inData >> props.health;
                inData >> props.maxMagic;
                inData >> props.magic;
                inData >> props.movement;
                inData >> props.speed;
                inData >> props.strength;
                inData >> props.defense;

                inData >> props.state;
                inData >> props.resistance;
                inData >> props.weakness;
                quint8 q;
                inData >> q;
                props.spriteRef = Charset(props.charsetPath);
                if (q == PLAYER_TEAM) {
                    k++;
                    m_playerTeam.append(new Unit(this, props, i, j, PLAYER_TEAM));
                    m_matrix[j][i].unit = m_playerTeam.last();
                    m_matrix[j][i].unit->setOrdre(ordre);
                }
                else {
                    k++;
                    int g;
                    inData >> g;
                    m_computerTeam.append(new UnitIA(this, props, i, j, COMPUTER_TEAM));   //I'm not a number, I'm a free man !
                    m_matrix[j][i].unit = m_computerTeam.last();
                    m_matrix[j][i].unit->setOrdre(ordre);
                    ((UnitIA*)m_matrix[j][i].unit)->setTargetNumber((quint8)g);
                }


                quint8 p;
                bool b;
                inData >> q;
                inData >> p;
                m_matrix[j][i].unit->setPos(q,p);


                inData >> q;
                m_matrix[j][i].unit->setRemainingMoves(q);
                inData >> b;
                m_matrix[j][i].unit->setRemainingAction(b);


            }

        }
    }
    int h;
    inData >> h;
    m_unitType = UnitType(h);

    QList<Unit*> temp;
    temp.append(m_playerTeam);
    temp.append(m_computerTeam);

    inData >> m_target;
    inData >> m_allowedMoves;

    for (int i=0; i < temp.size(); i++) {
        for (int j = 0; j < temp.size(); j++) {
            if (temp[j]->getOrdre() == quint8(i)) {
                m_units.append(temp[j]);
            }
        }

    }

    temp.clear();
    temp.append(m_computerTeam);

    for (int i=0; i < temp.size(); i++) {

        if ((((UnitIA*)temp[i])->getTargetNumber()) != 255) {
            ((UnitIA*)temp[i])->setTarget(m_units[(((UnitIA*)temp[i])->getTargetNumber())]);
        }
        else {
            ((UnitIA*)temp[i])->setTarget(NULL);
        }

    }



    m_file.close();


}

void GameMap::save() {

    m_file.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QDataStream outData(&m_file);

    //Writes the game name
    std::string name = m_name.toStdString();
    outData << name.c_str();
    qDebug() << name.c_str();

    //Writes the matrix dimensions
    outData << m_width;
    outData << m_height;


    //Pikachu !
    for(quint8 j = 0 ; j < m_height ; j++)
    {
        for(quint8 i = 0 ; i < m_width ; i++)
        {
            quint16 q;
            q = m_map.getTileNumber(j,i);
            outData << q;
        }
    }

    for (int i=0; i < m_units.size(); i++) {

        m_units[i]->setOrdre(quint8(i));
    }

    outData << m_playerTeam.getName();
    outData << m_playerTeam.getNumber();
    outData << m_computerTeam.getName();
    outData << m_computerTeam.getNumber();

    for(quint8 j = 0 ; j < m_height ; j++)
    {
        for(quint8 i = 0 ; i < m_width ; i++)
        {
            if (m_matrix[j][i].unit == NULL) {
                quint8 v = 0;
                outData << v;
            }
            else {
                quint8 v = 1;
                outData << v;

                outData  << m_matrix[j][i].unit->getOrdre();
                outData << m_matrix[j][i].unit->getProperties().charsetPath;

                outData << m_matrix[j][i].unit->getProperties().unitClass;
                outData << m_matrix[j][i].unit->getProperties().unitType;

                outData << m_matrix[j][i].unit->getProperties().priority;    //an unit with a high priority will play its turn sooner

                outData << m_matrix[j][i].unit->getProperties().level;
                outData << m_matrix[j][i].unit->getProperties().exp;

                outData << m_matrix[j][i].unit->getProperties().maxHealth;
                outData << m_matrix[j][i].unit->getProperties().health;
                outData << m_matrix[j][i].unit->getProperties().maxMagic;
                outData << m_matrix[j][i].unit->getProperties().magic;
                outData << m_matrix[j][i].unit->getProperties().movement;
                outData << m_matrix[j][i].unit->getProperties().speed;
                outData << m_matrix[j][i].unit->getProperties().strength;
                outData << m_matrix[j][i].unit->getProperties().defense;

                outData << m_matrix[j][i].unit->getProperties().state;
                outData << m_matrix[j][i].unit->getProperties().resistance;
                outData << m_matrix[j][i].unit->getProperties().weakness;

                outData << m_matrix[j][i].unit->getTeamNumber();

                if (m_matrix[j][i].unit->getTeamNumber() == COMPUTER_TEAM)
                {
                    qint32 index = (m_units.indexOf(((UnitIA*)m_matrix[j][i].unit)->getTarget()));
                    if (index == -1)
                        outData << 255;
                    else
                        outData << index;
                }
                outData << m_matrix[j][i].unit->getX(); //x position in tiles
                outData << m_matrix[j][i].unit->getY(); //y position in tiles

                outData << m_matrix[j][i].unit->getRemainingMoves();
                outData << m_matrix[j][i].unit->getRemainingAction();
            }

        }
    }


    outData << m_unitType;


    outData << m_target;
    outData << m_allowedMoves;

    m_file.close();

}

Map* GameMap::getMap()
{
    return &m_map;
}

Unit* GameMap::getUnit(quint8 x, quint8 y) const
{
    return m_matrix[y][x].unit;
}

const QList<Unit*>& GameMap::getUnits() const
{
    return m_units;
}

Unit* GameMap::getNextUnit()
{
    m_units.append(m_units.first());
    m_units.removeFirst();
    m_units.first()->newTurn();
    return m_units.first();
}

void GameMap::setUnit(quint8 x, quint8 y, Unit* unit)
{
    m_matrix[y][x].unit = unit;
}

bool GameMap::containsUnit(quint8 x, quint8 y) const
{
    if(x >= m_width || y >= m_height)
    {
        throw EX_OUT_OF_RANGE();
    }
    return m_matrix[y][x].unit != (Unit*)0;
}


QList<QPoint> GameMap::findPath(const QPoint& start, const QPoint& target, UnitType unitType, quint8 remainingMoves, bool forceFreeDestination)
{
    m_openedList.clear();
    m_closedList.clear();
    m_unitType = unitType;
    m_start.parent = start;
    m_target = target;
    m_forceFreeDestination = forceFreeDestination;

    m_allowedMoves = remainingMoves;
    QPoint current = start;

    m_openedList[current] = m_start;
    addClosedList(current);
    addAdjacentSquares(current);

    while(current != target && (!m_openedList.empty()))
    {
        current = bestOpenedListNode();
        addClosedList(current);
        addAdjacentSquares(current);
    }

    if(current == target)
    {
        return retrievePath();
    }
    else
    {
        return QList<QPoint>();
    }
}

quint8 GameMap::taxicabDistance(quint8 x1, quint8 y1, quint8 x2, quint8 y2)
{
    return abs(x1-x2) + abs(y1-y2);
}

quint8 taxicabDistance(QPoint p1, QPoint p2)
{
    return GameMap::taxicabDistance(p1.x(), p1.y(), p2.x(), p2.y());
}

CharDirection GameMap::direction(quint8 x1, quint8 y1, quint8 x2, quint8 y2)
{
    CharDirection dir;
    if(abs(x2 - x1) > abs(y2 - y1))   //horizontal
    {
        if(x2 > x1)
            dir = RIGHT;
        else
            dir = LEFT;
    }
    else    //vertical
    {
        if(y2 > y1)
            dir = DOWN;
        else
            dir = UP;
    }
    return dir;
}

void GameMap::addAdjacentSquares(QPoint n)
{
    AStarNode tmp;
    int tilePract = 0;
    int dist;
    for(qint8 i = n.x() - 1 ; i <= n.x() + 1 ; i++)
    {
        if(i >= 0 && i < m_width)
        {
            for(qint8 j = n.y() - 1 ; j <= n.y() + 1 ; j++)
            {
                if(j >= 0 && j < m_height && ((i == n.x()) xor (j == n.y())))
                {
                    tilePract = m_map.getTile(i,j).getPracticability(m_unitType);
                    dist = taxicabDistance(i, j, m_target.x(), m_target.y());
                    QPoint it(i,j);
                    if((tilePract != 0 && m_matrix[j][i].unit == (Unit*)0) || (it == m_target && m_forceFreeDestination))
                    {
                        if ((!m_closedList.contains(it)) &&
                                ((m_closedList.value(n).g_cost + tilePract + dist) <= m_allowedMoves))
                        {
                            tmp.g_cost = m_closedList.value(n).g_cost + tilePract;
                            tmp.h_cost = dist;
                            tmp.f_cost = tmp.g_cost + tmp.h_cost;
                            tmp.parent = n;

                            if(m_openedList.contains(it))
                            {
                                if(tmp.f_cost < m_openedList[it].f_cost)
                                {
                                    m_openedList[it] = tmp;
                                }
                            }
                            else
                            {
                                m_openedList[it] = tmp;
                            }
                        }
                    }
                }
            }
        }
    }
}

QPoint GameMap::bestOpenedListNode()
{
    qint8 b_fcost = m_openedList.begin().value().f_cost;
    QPoint b_node = m_openedList.begin().key();

    for(QMap<QPoint, AStarNode>::const_iterator i = m_openedList.begin() ; i != m_openedList.end() ; i++)
    {
        if(i.value().f_cost < b_fcost)
        {
            b_fcost = i.value().f_cost;
            b_node = i.key();
        }
    }

    return b_node;
}

bool operator<(const QPoint& p1, const QPoint& p2)
{
    return ((p1.x() < p2.x()) || ((p1.x() == p2.x()) && p1.y() < p2.y()));
}

void GameMap::addClosedList(QPoint p)
{
    if(m_openedList.contains(p))
    {
        const AStarNode& n = m_openedList[p];
        m_closedList[p] = n;
        m_openedList.remove(p);
    }
    else
        qDebug() << "Erreur, noeud pas dans liste ouverte";
}

QList<QPoint> GameMap::retrievePath()
{
    QList<QPoint> path;
    AStarNode tmp = m_closedList.value(m_target);

    QPoint n = m_target;
    QPoint prec = tmp.parent;

    path.push_front(n);

    while(prec != m_start.parent)
    {
        n = prec;
        path.push_front(n);

        tmp = m_closedList.value(tmp.parent);
        prec = tmp.parent;
    }

    return path;
}

void GameMap::DestroyUnit(Unit* u)
{
    quint8 unitTeam = u->getTeamNumber();
    if(!u->isIAControlled())
    {
        for(quint8 i = 0 ; i < m_units.size() ; i++)
        {
            if(m_units.at(i)->isIAControlled() && (((UnitIA*)(m_units.at(i)))->getTarget() == u))
            {
                ((UnitIA*)(m_units.at(i)))->setTarget(0);
            }
        }
    }


    quint8 x = u->getX();
    quint8 y = u->getY();
    if (u->isIAControlled()) {
        m_computerTeam.removeOne(u);
    }
    else {
        m_playerTeam.removeOne(u);
    }

    m_units.removeOne(u);
    delete m_matrix[y][x].unit;
    m_matrix[y][x].unit = (Unit*)0;

    bool lastUnit = true;
    quint8 i = 0;
    while(lastUnit && i < m_units.size())
    {
        if(m_units.at(i)->getTeamNumber() == unitTeam)
        {
            lastUnit = false;
        }
        i++;
    }
    m_gameOver = lastUnit;
    if (this->gameIsOver()) {
        equipePerdante = unitTeam;
    }
}
